Java中的BigDecimal和8种RoundingMode(舍入模式)分析 您所在的位置:网站首页 round it up和round it down有什么区别 Java中的BigDecimal和8种RoundingMode(舍入模式)分析

Java中的BigDecimal和8种RoundingMode(舍入模式)分析

2024-07-14 10:38| 来源: 网络整理| 查看: 265

前言

相信大家对Java中的基本数据类型都已经很熟悉了,每种类型处理不同的数据,但是当有一个特别大的数字需要处理的时候,并且要求准确的精度时,你可能需要用到BigDecimal类。

有什么用?

先来执行一段代码,看看结果

System.out.println(0.2 + 0.1); System.out.println(0.3 - 0.1); System.out.println(0.2 * 0.1); System.out.println(0.3 / 0.1);

执行结果如下:

0.30000000000000004 0.19999999999999998 0.020000000000000004 2.9999999999999996

对你没有看错,再执行一遍还是这个结果,奇怪了难道不应该是 0.3/0.2/0.2/0.3吗,这是为什么?

原因:

我们的计算机只能识别二级制,在进行数学计算时,参与计算的数据都是二进制,所以我们输入的十进制计算数据都会先转换为二进制,计算机用二进制计算完成后,再由二进制转换为十进制返回。

在这过程中,十进制转二进制时,有些数字是无法完全转换的,小数在转换为二进制时并不一定能用一个精确的二进制表示,大多数时候都是取的一个近似值,这就造成了精度的丢失。如果再用这个二进制进行计算,明显计算结果的精度会进一步丢失。终极原因还是因为精度丢失,

关于小数的二进制表示方法可以参考这里:《小数二进制的表示与转换》

针对这一问题,Java语言提供了java.math.BigDecimal类专门用来进行精确计算。

什么时候用?

在Java中float的精度为 7~ 8位有效数字,double精度为:16~17位有效数字,在不考虑精度丢失的情况下,如果要处理20位的数据或者30、40位的数据,此时就需要用到BigDecimal。

float和double只能用来做科学计算或者是工程计算,而且存在精度丢失问题,而涉及到较大数据,对精度有严格要求的计算中BigDecimal是首选,比如金融、银行业务、涉及到货币的业务上的计算都是使用BigDecimal进行数据处理的。

怎么用?

BigDecimal类被用在大数值的具体精度计算中,下面具体从如果创建一个BigDecimal对象,如何在多个BigDecimal对象间进行加减乘除计算等。

构造方法:

BigDecimal的构造方法有很多种,此处列举常用的几种

public BigDecimal(int val) public BigDecimal(long val) public BigDecimal(double val) public BigDecimal(String val)

通过这四个最基本的构造方法,即可创建Bigdecimal对象,但是这其中仍然有猫腻,且看下文,执行下面的代码:

public class BigDecimalDemo { public static void main(String[] args) { BigDecimal b1 = new BigDecimal(1); BigDecimal b2 = new BigDecimal(1.1); BigDecimal b3 = new BigDecimal(111L); BigDecimal b4 = new BigDecimal("1.1"); System.out.println(b1); System.out.println(b2); System.out.println(b3); System.out.println(b4); } }

执行结果如下:

1 1.100000000000000088817841970012523233890533447265625 111 1.1

很显然,b2的值不是我们所期望的,BigDecimal中也存在精度丢失问题,我们可以看看JDK源码中public BigDecimal(double val)的说明

参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入 newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。如果传入的参数必须是double类型时,可以通过 Double.toString(double b) 方法将double参数转换成字符串传入,或者直接使用 BigDecimal.valueOf(double b) 来进行接收处理

从源码中的方法注释可以看出,BigDecimal在处理double型数据时确实会出现精度问题,Java推荐我们使用String类型来作为构造方法的的入参。

加减乘除:

BigDecimal之间的运算不是基本数据类型的普通运算,而是自行封装好的四则运算,方法如下:

//加法 public BigDecimal add(BigDecimal value); //减法 public BigDecimal subtract(BigDecimal value); //乘法 public BigDecimal multiply(BigDecimal value); //除法 public BigDecimal divide(BigDecimal value);

来个小栗子:

public class BigDecimalDemo { public static void main(String[] args) { BigDecimal a = new BigDecimal("6.6"); BigDecimal b = new BigDecimal("1.1"); System.out.println("a + b =" + a.add(b)); System.out.println("a - b =" + a.subtract(b)); System.out.println("a * b =" + a.multiply(b)); System.out.println("a / b =" + a.divide(b)); } }

输出结果如下:

a + b =7.7 a - b =5.5 a * b =7.26 a / b =6 其他方法: //保留小数位(newScale:保留几位小数,roundingMode:舍入模式) BigDecimal setScale(int newScale, int roundingMode) //取余运算 BigDecimal remainder(BigDecimal divisor); //求相反数 BigDecimal negate(); //比较大小(左边比右边数大返回1,相等返回0,比右边小返回-1) int compareTo(BigDecimal val); //将BigDecimal对象中的值以整数返回 int intValue() //将BigDecimal对象中的值以double类型返回 double doubleValue() //将BigDecimal对象中的值以float类型返回 float floatValue() //将BigDecimal对象中的值以long类型返回 long longValue() //将BigDecimal对象中的值以String类型返回 String toString()

例子如下:

public class BigDecimalDemo { public static void main(String[] args) { BigDecimal a = new BigDecimal("6.664"); BigDecimal b = new BigDecimal("3"); System.out.println("a四舍入五保留两位小数:"+a.setScale(2,RoundingMode.HALF_UP)); System.out.println("a取余运算:"+a.remainder(b)); System.out.println("a求相反数:"+a.negate()); System.out.println("a,b比大小:"+a.compareTo(b)); System.out.println("a以整数形式输出:"+a.intValue()); System.out.println("a以double形式输出:"+a.doubleValue()); System.out.println("a以float形式输出:"+a.floatValue()); System.out.println("a以long形式输出:"+a.longValue()); System.out.println("a以String形式输出:"+a.toString()); } }

输出如下:

a四舍入五保留两位小数:6.66 a取余运算:0.664 a求相反数:-6.664 a,b比大小:1 a以整数形式输出:6 a以double形式输出:6.664 a以float形式输出:6.664 a以long形式输出:6 a以String形式输出:6.664 舍入模式说明:

在BigDecimal中可以对高精度的浮点数根据不同的舍入模式进行数位保留操作。

舍入模式(Rounding Mode):是BigDecimal类中的静态变量,其中包括了八种常见舍入规则,比如四舍五入法、银行家舍入法等。

下面具体说明下舍入模式的分类

ROUND_UP

向远离零的方向舍入。舍弃非零部分,并将非零舍弃部分相邻的一位数字加一

ROUND_DOWN

向接近零的方向舍入。舍弃非零部分,同时不会非零舍弃部分相邻的一位数字加一,采取截取行为。

ROUND_CEILING

向正无穷的方向舍入。如果为正数,舍入结果同ROUND_UP一致;如果为负数,舍入结果同ROUND_DOWN一致。注意:此模式不会减少数值大小。

ROUND_FLOOR

向负无穷的方向舍入。如果为正数,舍入结果同ROUND_DOWN一致;如果为负数,舍入结果同ROUND_UP一致。注意:此模式不会增加数值大小。

ROUND_HALF_UP

向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。 如果舍弃部分>= 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。 这种模式也就是我们常说的我们的“四舍五入”。

ROUND_HALF_DOWN

向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向下舍入的舍入模式。 如果舍弃部分> 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。 这种模式也就是我们常说的我们的“五舍六入”。

ROUND_HALF_EVEN

向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则相邻的偶数舍入。 如果舍弃部分左边的数字奇数,则舍入行为与 ROUND_HALF_UP 相同; 如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。 注意:在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。 此舍入模式也称为“银行家舍入法”,主要在美国使用。 四舍六入,被舍位为5时两种情况,如果前一位为奇数,则入位,否则舍去。

ROUND_UNNECESSARY

断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

空说理论看着头大,来一段示例代码

public class BigDecimalDemo { public static void main(String[] args) { BigDecimal aa = new BigDecimal("3.300"); BigDecimal bb = new BigDecimal("3.344"); BigDecimal cc = new BigDecimal("3.355"); BigDecimal dd = new BigDecimal("3.356"); BigDecimal ee = new BigDecimal("3.366"); demo(aa,2); demo(bb,2); demo(cc,2); demo(dd,2); demo(ee,2); } public static void demo(BigDecimal bigDecimal,int scale){ System.out.println(); System.out.print(bigDecimal.toString()+"\t"); //循环使用8种舍入模式 for (int i =0 ;i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有